﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using DomainDriver.DomainModeling.Constraints;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.DomainModels;
using DomainDriver.DomainModeling.DomainObjects;
using DomainDriver.DomainModeling.Exceptions;
using DomainDriver.DomainModeling.Queries;
using DomainDriver.DomainModeling.Repositories;

namespace DomainDriver.DomainModeling.GenericUnitTesting.Constraints
{
    public abstract class IndexConstraintTestFixture<DOMAIN_MODEL, KEY, KEYED_DOMAIN_OBJECT> : ConstraintTestFixture<DOMAIN_MODEL, IndexConstraint<KEY, KEYED_DOMAIN_OBJECT>>
        where DOMAIN_MODEL : class, IDomainModel
        where KEYED_DOMAIN_OBJECT : IKeyedDomainObject<KEY, KEYED_DOMAIN_OBJECT>
    {
        protected List<KEY> m_RootLevelKeys;
        protected Dictionary<KEY, int> m_ChildrenPerRootLevelKey;

        public IndexConstraintTestFixture(DataSourcePersistenceType persistenceType)
            : base(persistenceType)
        {
            // do nothing
        }

        [SetUp]
        public override void SetUp()
        {
            base.SetUp();
            m_RootLevelKeys = new List<KEY>();
            m_ChildrenPerRootLevelKey = new Dictionary<KEY, int>();
        }

        protected virtual IRepository<KEY, KEYED_DOMAIN_OBJECT> IndexedRepository
        {
            get { return DomainModel.GetRepository<KEY, KEYED_DOMAIN_OBJECT>(); }
        }

        protected virtual IndexConstraint<KEY, KEYED_DOMAIN_OBJECT> IndexConstraint
        {
            get { return DomainModel.GetConstraint<IndexConstraint<KEY, KEYED_DOMAIN_OBJECT>>(); }
        }

        protected abstract int GetIndexValue(KEYED_DOMAIN_OBJECT domainObject);
        protected abstract KEYED_DOMAIN_OBJECT CreateDomainObjectWithCorrectIndexValue();
        protected abstract KEYED_DOMAIN_OBJECT CreateDomainObjectWithIncorrectIndexValue(ref bool hasIndexSetter);
        protected abstract void UpdateDomainObjectIndex(KEYED_DOMAIN_OBJECT domainObject, ref bool hasIndexSetter);
        protected abstract void CreateMultipleObjects();
        protected abstract KEYED_DOMAIN_OBJECT CreateChildObject(KEY parentDomainObjectKey);
        protected abstract IQuery<KEYED_DOMAIN_OBJECT> GetQueryForRootObjects();

        [Test]
        public virtual void VerifyConstraintOnAddingCorrectValue_POSITIVE()
        {
            KEYED_DOMAIN_OBJECT originalObject = default(KEYED_DOMAIN_OBJECT);
            KEYED_DOMAIN_OBJECT copyObject = default(KEYED_DOMAIN_OBJECT);
            KEYED_DOMAIN_OBJECT reloadedObject = default(KEYED_DOMAIN_OBJECT);

            originalObject = CreateDomainObjectWithCorrectIndexValue();
            copyObject = originalObject.Copy();

            IndexedRepository.Add(originalObject);

            originalObject = IndexedRepository.Read(originalObject.Key);
            Assert.AreEqual(originalObject, copyObject);

            SaveAndReloadDomainModel();

            reloadedObject = IndexedRepository.Read(originalObject.Key);
            Assert.AreEqual(reloadedObject, copyObject);
        }

        [Test]
        public virtual void VerifyConstraintOnAddingIncorrectValue_POSITIVE()
        {
            bool hasIndexSetter = false;
            KEYED_DOMAIN_OBJECT originalObject = default(KEYED_DOMAIN_OBJECT);
            KEYED_DOMAIN_OBJECT copyObject = default(KEYED_DOMAIN_OBJECT);
            KEYED_DOMAIN_OBJECT reloadedObject = default(KEYED_DOMAIN_OBJECT);

            originalObject = CreateDomainObjectWithIncorrectIndexValue(ref hasIndexSetter);
            copyObject = originalObject.Copy();

            Exception addException = null;
            try
            { IndexedRepository.Add(originalObject); }
            catch (Exception exception)
            { addException = exception; }

            if (hasIndexSetter)
            {
                originalObject = IndexedRepository.Read(originalObject.Key);
                Assert.IsNull(addException);
                Assert.AreNotEqual(originalObject, copyObject);
                copyObject = originalObject.Copy();
            }
            else
            {
                Assert.IsNotNull(addException);
                Assert.IsInstanceOf(typeof(DomainObjectManagementException), addException);
                Assert.AreEqual(originalObject, copyObject);
            }

            SaveAndReloadDomainModel();

            if (hasIndexSetter)
            {
                reloadedObject = IndexedRepository.Read(originalObject.Key);
                Assert.AreEqual(reloadedObject, copyObject);
            }
            else
            {
                Assert.IsFalse(IndexedRepository.Exists(originalObject.Key));
            }
        }

        [Test]
        public virtual void VerifyConstraintOnUpdating_POSITIVE()
        {
            bool hasIndexSetter = false;
            KEYED_DOMAIN_OBJECT originalObject = default(KEYED_DOMAIN_OBJECT);
            KEYED_DOMAIN_OBJECT copyObject = default(KEYED_DOMAIN_OBJECT);
            KEYED_DOMAIN_OBJECT reloadedObject = default(KEYED_DOMAIN_OBJECT);

            originalObject = CreateDomainObjectWithCorrectIndexValue();
            copyObject = originalObject.Copy();

            IndexedRepository.Add(originalObject);

            SaveAndReloadDomainModel();

            originalObject = IndexedRepository.Read(originalObject.Key);
            Assert.AreEqual(originalObject, copyObject);
            copyObject = originalObject.Copy();

            UpdateDomainObjectIndex(originalObject, ref hasIndexSetter);

            Exception updateException = null;
            try
            {
                IndexedRepository.Update(originalObject);

                SaveAndReloadDomainModel();
            }
            catch (Exception exception)
            { updateException = exception; }

            if (!hasIndexSetter)
            {
                Assert.IsNull(updateException);
                reloadedObject = IndexedRepository.Read(originalObject.Key);
                Assert.AreEqual(reloadedObject, originalObject);
            }
            else
            {
                Assert.IsNotNull(updateException);
                Assert.IsInstanceOf(typeof(DomainObjectManagementException), updateException);
                reloadedObject = IndexedRepository.Read(originalObject.Key);
                Assert.AreEqual(reloadedObject, copyObject);
            }
        }

        [Test]
        public void VerifyConstraintOnAddingMultipleObjects_POSITIVE()
        {
            int firstIndexValue = IndexConstraint<KEY, KEYED_DOMAIN_OBJECT>.IndexStartValue;

            CreateMultipleObjects();

            KEYED_DOMAIN_OBJECT dObjParentNull = CreateDomainObjectWithCorrectIndexValue();
            IndexedRepository.Add(dObjParentNull);
            dObjParentNull = IndexedRepository.Read(dObjParentNull.Key);

            IQuery<KEYED_DOMAIN_OBJECT> query = GetQueryForRootObjects();
            ICollection<KEYED_DOMAIN_OBJECT> matchingObjects = IndexedRepository.Read(query);

            Nullable<int> maxExistingIndex = matchingObjects.Max<KEYED_DOMAIN_OBJECT>((KEYED_DOMAIN_OBJECT dObj) => GetIndexValue(dObj));
            int expectedIndexValue = (maxExistingIndex.HasValue) ? (maxExistingIndex.Value) : firstIndexValue;
            int actualIndexValue = GetIndexValue(dObjParentNull);
            Assert.AreEqual(expectedIndexValue, actualIndexValue);

            if (1 > DomainModel.GetConstraint<IndexConstraint<KEY, KEYED_DOMAIN_OBJECT>>().GetPropertyNamesUniqueOn().Count)
            { return; }

            for (int index = 0; index < m_RootLevelKeys.Count; index++)
            {
                KEY parentKey = m_RootLevelKeys[index];
                KEYED_DOMAIN_OBJECT dObjParentByIndex = CreateChildObject(parentKey);
                IndexedRepository.Add(dObjParentByIndex);

                SaveAndReloadDomainModel();
                dObjParentByIndex = IndexedRepository.Read(dObjParentByIndex.Key);

                expectedIndexValue = firstIndexValue + m_ChildrenPerRootLevelKey[parentKey];
                actualIndexValue = GetIndexValue(dObjParentByIndex);
                Assert.AreEqual(expectedIndexValue, actualIndexValue);
            }
        }
    }
}